package com.xiyuan.smartutils.logging;

import com.xiyuan.smartutils.StrUtils;
import com.xiyuan.smartutils.Systems;
import com.xiyuan.smartutils.Validates;

import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;

/**
 * the description for class LogAppender
 *
 * @author xiyuan-lgz 2022-10-20 @version v1.0.0 新建与整理
 */
public abstract class LogAppender {
    private static final String CAUSE_CAPTION = "Caused by: ";
    private static final String SUPPRESSED_CAPTION = "Suppressed: ";
    
    // 消息格式化
    protected static final String NEW_LINE = System.lineSeparator();
    
    protected LogConfig config = new LogConfig();
    
    /**
     * 是否已经输出 消息内容
     */
    
    protected LogAppender setConfig(LogConfig config) {
        if (config != null) {
            this.config = config;
        }
        return this;
    }
    
    public LogConfig getConfig() {
        return config;
    }
    
    
    protected void print(LogRecord record, PrintStream out) throws Exception {
        boolean messaged = false, endNewline = false;
        String pt = config.getPattern();
        // StringBuilder strb = new StringBuilder(128);
        final int limit = pt.length();
        boolean flag = false;
        for (int i = 0; i < limit; i++) {
            char ch = pt.charAt(i);
            if (Validates.isWhitespace(ch)) {
                endNewline = false;
            }
            
            if (!flag && ch == '%') {
                flag = true;
                continue;
            }
            
            
            if (!flag) {
                out.print(ch);
                // strb.append(ch);
                continue;
            }
            
            
            // %d{yyyy-MM-dd hh:mm:ss,fff}  配置时间 {中间是表达式}
            // %p   输出 日志级别
            // %t   输出线程名
            // %c   输出调用者类名
            // %m   输出调用者方法名
            // %l   输出调用者方法 所在代码行号
            // %n   输出一个回车换行符，Windows平台为”\r\n”，Unix平台为”\n”输出日志信息换行
            // %%   输出一个‘%’
            // %M   输出日志内容
            switch (ch) {
                case 'd':
                case 'D': {
                    // 格式化
                    int start = -1, end = 0;
                    for (int j = i + 1; j < limit; j++) {
                        char c = pt.charAt(j);
                        if (start == -1 && c == '{') {
                            start = j + 1;
                            continue;
                        }
                        
                        if (start != -1 && c == '}') {
                            end = j;
                            i = j;
                            break;
                        }
                    }
                    
                    if (end > 0) {
                        SimpleDateFormat df = new SimpleDateFormat(StrUtils.trim(pt.substring(start, end)));
                        // strb.append(df.format(record.getTime()));
                        out.print(df.format(record.getTime()));
                        flag = false;
                    }
                    else {
                        out.print(ch);
                        // strb.append(ch);
                    }
                    break;
                }
                
                case 'c':
                case 'C':
                    
                    // strb.append(record.getClazz());
                    out.print(record.getClazz());
                    flag = false;
                    break;
                case 'm':
                    out.print(record.getMethod());
                    // strb.append(record.getMethod());
                    flag = false;
                    break;
                case 'M':
                    printMessage(out, record.getMessage(), record.getParams());
                    // strb.append(record.getMessage());
                    flag = false;
                    messaged = true;
                    break;
                case 'l':
                case 'L':
                    out.print(record.getSourceLineNumber());
                    // strb.append(record.getSourceLineNumber());
                    flag = false;
                    break;
                case 't':
                case 'T':
                    out.print(Thread.currentThread().getName());
                    // strb.append(Thread.currentThread().getName());
                    flag = false;
                    break;
                case 'p':
                    out.print(record.getLevel().toString().toUpperCase());
                    // strb.append(record.getLevel().toString().toUpperCase());
                    flag = false;
                    break;
                case 'P':
                    out.print(record.getLevel().toString().toLowerCase());
                    // strb.append(record.getLevel().toString().toLowerCase());
                    flag = false;
                    break;
                case 'n':
                    // strb.append(NEW_LINE);
                    flag = false;
                    out.print(NEW_LINE);
                    break;
                case '%':
                    out.print("%");
                    // strb.append("%");
                    ch = 0;
                    flag = false;
                    break;
                default:
                    flag = false;
                    out.print(ch);
                    // strb.append(ch);
                    break;
            }
        }
        if (!messaged) {
            // strb.append(" : ");
            out.print(" : ");
            printMessage(out, record.getMessage(), record.getParams());
            out.print(NEW_LINE);
            endNewline = true;
        }
        if (!endNewline && record.getThrown() != null) {
            out.print(NEW_LINE);
        }
        appendThrown(record.getThrown(), out);
        out.flush();
    }
    
    private void printMessage(PrintStream out, CharSequence message, List<Object> params) throws Exception {
        if (message == null) {
            return;
        }
        if (params == null || params.isEmpty()) {
            out.print(message);
            return;
        }
        
        StringBuilder sb = new StringBuilder(4);
        StringBuilder ix = new StringBuilder(6);
        boolean open = false;
        int len = message.length();
        for (int i = 0; i < len; i++) {
            char ch = message.charAt(i);
            // if (!open) {
            //     sb.append(ch);
            // }
            
            switch (ch) {
                case '{': {
                    if (open) {
                        // 处理 { { 情况
                        out.print(ch);
                        out.print(sb);
                        out.print(ix);
                        sb.setLength(0);
                        ix.setLength(0);
                    }
                    open = true;
                    break;
                }
                case '}': {
                    if (!open) {
                        out.append(ch);
                        break;
                    }
                    // out.print(sb);
                    int index = -1;
                    try {
                        index = ix.length() == 0 || ix.length() > 10 ? -1 : Integer.parseInt(ix.toString());
                    }
                    catch (Throwable e) {}
                    if (index < params.size() && index >= 0) {
                        sb.setLength(0);
                        ix.setLength(0);
                        out.print(String.valueOf(params.get(index)));
                    }
                    else if (index < 0) {
                        out.print(sb);
                        out.print(ix);
                        out.print(ch);
                    }
                    
                    sb.setLength(0);
                    ix.setLength(0);
                    open = false;
                    break;
                }
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    if (!open) {
                        if (sb.length() > 0) out.print(sb);
                        if (ix.length() > 0) out.print(ix);
                        out.print(ch);
                        sb.setLength(0);
                        ix.setLength(0);
                        open = false;
                    }
                    else {
                        ix.append(ch);
                    }
                    break;
                default: {
                    if (open && Validates.isWhitespace(ch)) {
                        sb.append(ch);
                    }
                    else {
                        // 处理 { 12b 直接结束
                        if (open) out.print('{');
                        if (sb.length() > 0) out.print(sb);
                        if (ix.length() > 0) out.print(ix);
                        out.print(ch);
                        sb.setLength(0);
                        ix.setLength(0);
                        open = false;
                    }
                    break;
                }
            }
        }
        
    }
    
    /**
     * 增加日志到日志增加器中
     */
    public abstract void append(LogRecord record) throws Exception;
    
    
    protected final void appendThrown(Throwable e, final PrintStream printer) throws Exception {
        if (e == null) {
            return;
        }
        // Print this
        printer.println(e);
        
        Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        dejaVu.add(e);
        synchronized (printer) {
            try {
                // Print our stack trace
                StackTraceElement[] trace = e.getStackTrace();
                for (StackTraceElement traceElement : trace) {
                    printer.println("\tat " + traceElement);
                }
                
                // Print suppressed exceptions, if any
                for (Throwable se : e.getSuppressed()) {
                    printEnclosedStackTrace(printer, trace, SUPPRESSED_CAPTION, "\t", dejaVu, se);
                }
                
                // Print cause, if any
                Throwable ourCause = e.getCause();
                if (ourCause != null) {
                    printEnclosedStackTrace(printer, trace, CAUSE_CAPTION, "", dejaVu, ourCause);
                }
            }
            finally {
                printer.println();
            }
        }
        
    }
    
    private void printEnclosedStackTrace(PrintStream printer,
                                         StackTraceElement[] enclosingTrace,
                                         String caption,
                                         String prefix,
                                         Set<Throwable> dejaVu,
                                         Throwable cause) {
        
        if (dejaVu.contains(cause)) {
            printer.println("\t[CIRCULAR REFERENCE:" + cause + "]");
        }
        else {
            // 防止死循环
            dejaVu.add(cause);
            
            // Compute number of frames in common between this and enclosing trace
            StackTraceElement[] trace = cause.getStackTrace();
            int m = trace.length - 1;
            int n = enclosingTrace.length - 1;
            while (m >= 0 && n >= 0 && trace[m].equals(enclosingTrace[n])) {
                m--;
                n--;
            }
            int framesInCommon = trace.length - 1 - m;
            
            // Print our stack trace
            printer.println(prefix + caption + cause);
            for (int i = 0; i <= m; i++) {
                printer.println(prefix + "\tat " + trace[i]);
            }
            
            if (framesInCommon != 0) {
                printer.println(prefix + "\t... " + framesInCommon + " more");
            }
            
            // Print suppressed exceptions, if any
            for (Throwable se : cause.getSuppressed()) {
                printEnclosedStackTrace(printer, trace, SUPPRESSED_CAPTION, prefix + "\t", dejaVu, se);
            }
            
            // Print cause, if any
            Throwable ourCause = cause.getCause();
            if (ourCause != null) {
                printEnclosedStackTrace(printer, trace, CAUSE_CAPTION, prefix, dejaVu, ourCause);
            }
        }
    }
}
